/* See LICENSE for copyright and terms of use */

import org.evtmng.tween.Tween;
import org.evtmng.util.List;

/**
 * <p>Data structure to manage tweens.</p>
 *
 * <p>Contains a locking mechanism to prevent modifications to the lists of tweens
 * in the instance (methods that are lockable are found in {@link #LOCKABLES}).</p>
 *
 * @author Tay Ray Chuan
 */

class org.evtmng.timeline.Timeline {

	//******************************************************
  //*           Constants
  //******************************************************

	private static var LOCKABLES:Array = [
	"addTween",
	"removeTween"
	];
	private static var LOCK_PREFIX:String = "$$";
	private var m_isLocked:Boolean;

	/**
	 * <p>Contains all tweens for this class, identified by {@link org.evtmng.Tween#id()}, such that
	 * the resulting structure is:</p>
	 *
	 * <p>
	 * <code>list</code> {
	 * &lt;{@link org.evtmng.Tween#id()}&gt;: tweenInstance
	 * ...
	 * }
	 * </p>
	 */
	private var m_tweens:List;

	/**
	 * <p>Contains all tweens for this class. They are grouped by {@link org.evtmng.Tween#start()},
	 * such that the resulting structure is:</p>
	 *
	 *  <p>
	 *  <code>List</code> {
	 * &lt;{@link org.evtmng.Tween#start()}&gt;: List{&lt;{@link org.evtmng.Tween#id()}&gt;: tweenInstance...},
	 * ...
	 * }
	 * </p>
	 */
	private var m_starts:List;

	/**
	 * <p>Contains all tweens for this class. They are grouped by {@link org.evtmng.Tween#end()},
	 * such that the resulting structure is:</p>
	 *
	 *  <p>
	 *  <code>List</code> {
	 * &lt;{@link org.evtmng.Tween#end()}&gt;: List{&lt;{@link org.evtmng.Tween#id()}&gt;: tweenInstance...},
	 * ...
	 * }
	 * </p>
	 */
	private var m_ends:List;

	public function Timeline() {
		m_tweens = new List();
		m_starts = new List();
		m_ends = new List();

		m_isLocked = false;
	}

	//******************************************************
  //*           Adding Items
  //******************************************************

	/**
	 * <p>Adds a tween.
	 *
	 * @throws <code>Error</code> if the tween has already been added.
	 */
	public function addTween(f:Tween):Void {
		m_tweens.addItemWithId(f, f.id());
		addStart(f);
		addEnd(f);
	}

	private function addTimeToListForTween(time:Number, list:List, f:Tween):Void {
		if(list.itemForId(time)==null) {
			list.addItemWithId(new List(), time);
		}
		var inner:List = List(list.itemForId(time));
		inner.addItemWithId(f, f.id());
	}

	private function addStart(f:Tween):Void {
		addTimeToListForTween(f.start(), m_starts, f);
	}

	private function addEnd(f:Tween):Void {
		addTimeToListForTween(f.end(), m_ends, f);
	}

	//******************************************************
  //*           Accessing Items
  //******************************************************

  private function tweensForTimeList(time:Number, list:List):List {
  	return List(list.itemForId(time));
  }

	/**
	 * @param s
	 * @return Returns the {@link org.evtmng.List} of tweens for the {@link org.evtmng.Tween#start()}
	 * time <code>s</code>
	 */
  public function tweensForStart(s:Number):List {
  	return tweensForTimeList(s, m_starts);
  }

	/**
	 * @param e
	 * @return Returns the {@link org.evtmng.List} of tweens for the {@link org.evtmng.Tween#end()}
	 * time <code>e</code>
	 */
  public function tweensForEnd(e:Number):List {
  	return tweensForTimeList(e, m_ends);
  }

	/**
	 * @return Returns the value of {@link org.evtmng.Tween#start()} that is the smallest
	 * in the {@link org.evtmng.List} of tweens.
	 */
  public function firstStart():Number {
  	var internal:Object = m_starts;
  	var first: Number;
  	for(var i:String in internal) {
  		if(i<first || first == null) {
  			first = parseInt(i);
  		}
  	}
  	return first;
  }

	/**
	 * @return Returns the value of {@link org.evtmng.Tween#end()} that is the largest
	 * in the {@link org.evtmng.List} of tweens.
	 */
  public function lastEnd():Number {
  	var internal:Object = m_ends;
  	var last:Number;
  	for(var i:String in internal) {
  		if(i>last || last == null) {
  			last = parseInt(i);
  		}
  	}
  	return last;
  }

	public function tweens():List {
		return m_tweens;
	}

	public function startTweens():List {
		return m_starts;
	}

	public function endTweens():List {
		return m_ends;
	}

	//******************************************************
  //*           Removing Items
  //******************************************************

	/**
	 * @param f
	 * Removes the tween <code>f</code>.
	 */
  public function removeTween(f:Tween):Void {
  	m_tweens.removeItemWithId(f.id());
		removeStartForTween(f);
		removeEndForTween(f);
  }

  private function removeTweenForTimeList(f:Tween, time:Number, list:List):Void {
  	List(list.itemForId(time)).removeItemWithId(f.id());
  }

  private function removeStartForTween(f:Tween):Void {
  	removeTweenForTimeList(f, f.start(), m_starts);
  }

  private function removeEndForTween(f:Tween):Void {
  	removeTweenForTimeList(f, f.end(), m_ends);
  }

	//******************************************************
  //*           Access Permissions
  //******************************************************

  private function moveToForFunctionsReplace(from:String, to:String, funcs:Array, rep:Function):Void {
  	var f:Function, mtd:String;
  	for(var i:String in funcs) {
  		mtd = funcs[i];
  		f = this[from+mtd];
  		this[to+mtd] = f;
  		this[from+mtd] = rep;
  	}
  }

	/**
	 * <p>"Locks" the methods specified in {@link #LOCKABLES}, by replacing this methods with a dummy
	 * method.</p>
	 */
  public function lock():Void {
  	m_isLocked = true;
  	var replacement:Function = function():Void {
  		trace("Method unavaiable: timeline has been locked");
  	};
  	moveToForFunctionsReplace("", LOCK_PREFIX, LOCKABLES, replacement);
  }

	/**
	 * <p>Unlocks the methods specified in {@link #LOCKABLES}, by restoring these methods.</p>
	 */
  public function unlock():Void {
  	m_isLocked = false;

  	moveToForFunctionsReplace(LOCK_PREFIX, "", LOCKABLES, null);
  }

	/**
	 * @return Returns if the methods specified in {@link #LOCKABLES} have been locked.</p>
	 */
  public function isLocked():Boolean {
  	return m_isLocked;
  }
}